From 8fa41464216593bcbbd22c576e77c7cc025cc2f3 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Mon, 20 Apr 2020 13:40:23 +0100 Subject: [PATCH] Import dropwatch_1.5.3.orig.tar.xz [dgit import orig dropwatch_1.5.3.orig.tar.xz] --- .travis.yml | 19 + COPYING | 340 ++++++++++++ Makefile.am | 28 + README.md | 54 ++ autogen.sh | 5 + configure.ac | 40 ++ doc/Makefile.am | 2 + doc/dropwatch.1 | 80 +++ doc/dwdump.1 | 83 +++ spec/dropwatch.spec | 57 ++ src/Makefile.am | 14 + src/dwdump.c | 774 +++++++++++++++++++++++++++ src/lookup.c | 99 ++++ src/lookup.h | 54 ++ src/lookup_bfd.c | 63 +++ src/lookup_kas.c | 147 ++++++ src/main.c | 1155 +++++++++++++++++++++++++++++++++++++++++ src/net_dropmon.h | 126 +++++ tests/Makefile.am | 2 + tests/rundropwatch.sh | 19 + 20 files changed, 3161 insertions(+) create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README.md create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 doc/Makefile.am create mode 100644 doc/dropwatch.1 create mode 100644 doc/dwdump.1 create mode 100644 spec/dropwatch.spec create mode 100644 src/Makefile.am create mode 100644 src/dwdump.c create mode 100644 src/lookup.c create mode 100644 src/lookup.h create mode 100644 src/lookup_bfd.c create mode 100644 src/lookup_kas.c create mode 100644 src/main.c create mode 100644 src/net_dropmon.h create mode 100644 tests/Makefile.am create mode 100755 tests/rundropwatch.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1bc69dc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: c +dist: xenial + +compiler: + - clang + - gcc +addons: + apt: + packages: + binutils-dev + libreadline-dev + libnl-3-dev + libnl-genl-3-dev + libpcap-dev + +script: ./autogen.sh && ./configure && make && make check + +after_script: cat ./tests/rundropwatch.sh.log + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..fb2cfd6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,28 @@ +# Makefile.am -- +# Copyright 2018 Neil Horman +# All Rights Reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Authors: +# Steve Grubb +# + +AUTOMAKE_OPTIONS = no-dependencies +ACLOCAL_AMFLAGS = -I m4 +EXTRA_DIST = COPYING autogen.sh + +SUBDIRS = src doc tests + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a239aa --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +DropWatch +========= + +[![Build Status](https://travis-ci.org/nhorman/dropwatch.svg?branch=master)](https://travis-ci.org/nhorman/dropwatch) + +Thanks for Downloading Dropwatch! + +What is Dropwatch? +------------------ +Dropwatch is a project I started in an effort to improve the ability for +developers and system administrator to diagnose problems in the Linux Networking +stack, specifically in our ability to diagnose where packets are getting +dropped. From my probing, I've come to the conclusion that there are four main +shortcomings in our current environment: + +1) _Consolidation, or lack thereof._ Currently, if you would like to check on the +status of dropped packets in the kernel, you need to check at least 3 places, +and possibly more: The /proc/net/snmp file, the netstat utility, the tc utility, +and ethtool. This project aims to consolidate several of those checks into one +tool, making it easier for a sysadmin or developer to detect lost packets + +2) _Clarity of information._ Dropped packets are not obvious. A sysadmin needs +to be intimately familiar with each of the above tools to understand which +events or statistics correlate to a dropped packet and which do not. While that +is often self evident, it is also often not. Dropwatch aims to improve that +clarity + +3) _Ambiguity._ Even when a dropped packet is detected, the causes for those +dropped packets are not always clear. Does a UDPInError mean the application +receive buffer was full, or does it mean its checksum was bad? Dropwatch +attempts to disambiguate the causes for dropped packets. + +4) _Performance._ Utilities can be written to aggregate the data in the various +other utilities to solve some of these problems, but such solutions require +periodic polling of several interfaces, which is far from optimal, especially +when lost packets are rare. This solution improves on the performance aspect by +implementing a kernel feature which allows asynchronous notification of dropped +packets when they happen. + +Building Dropwatch +------------------ +Dropwatch uses the autotools suite (autoconf/automake) to build. To build and install the utility run the following commands: +``` +./autogen.sh +./configure +make +make install +``` + +Questions +--------- +Feel free to email me directly at nhorman@redhat.com with question, or if you +find a bug, open a trac ticket here on the github page + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..b792e8b --- /dev/null +++ b/autogen.sh @@ -0,0 +1,5 @@ +#! /bin/sh +set -x -e +mkdir -p m4 +# --no-recursive is available only in recent autoconf versions +autoreconf -fv --install diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..ad91702 --- /dev/null +++ b/configure.ac @@ -0,0 +1,40 @@ +AC_INIT(dropwatch,1.5.3) +AC_PREREQ(2.12)dnl +AM_CONFIG_HEADER(config.h) + +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign] [subdir-objects]) +AM_PROG_LIBTOOL +AC_SUBST(LIBTOOL_DEPS) + +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_AWK + +AC_CHECK_FUNCS(getopt_long) + +PKG_CHECK_MODULES([LIBNL3], [libnl-3.0], [], [AC_MSG_ERROR([libnl-3.0 is required])]) +# Fallback on using -lreadline as readline.pc is only available since version 8.0 +PKG_CHECK_MODULES([READLINE], [readline], [], [READLINE_LIBS=-lreadline]) +PKG_CHECK_MODULES([LIBPCAP], [libpcap], [], [ + AC_CHECK_LIB(pcap, pcap_open_live,[], + [AC_MSG_ERROR([libpcap is required])])]) + +AC_ARG_WITH([bfd], + [AS_HELP_STRING([--without-bfd], [Build without bfd library (default: yes)])], + [with_bfd=$withval], + [with_bfd=yes]) +AS_IF([test "x$with_bfd" != "xno"], [ + AC_CHECK_HEADERS([bfd.h], [], [AC_MSG_ERROR([Couldn't find or include bfd.h])]) +]) +AM_CONDITIONAL(USE_BFD, test "x$with_bfd" != "xno") + +AC_OUTPUT(Makefile src/Makefile doc/Makefile tests/Makefile) + +AC_MSG_NOTICE() +AC_MSG_NOTICE([dropwatch Version: $VERSION]) +AC_MSG_NOTICE([Target: $target]) +AC_MSG_NOTICE([Installation prefix: $prefix]) +AC_MSG_NOTICE([Compiler: $CC]) +AC_MSG_NOTICE([Compiler flags: $CFLAGS]) +AC_MSG_NOTICE([BFD library support: $with_bfd]) diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..6ff1152 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,2 @@ +dist_man_MANS = dropwatch.1 + diff --git a/doc/dropwatch.1 b/doc/dropwatch.1 new file mode 100644 index 0000000..eb52100 --- /dev/null +++ b/doc/dropwatch.1 @@ -0,0 +1,80 @@ +.TH dropwatch "1" "Mar 2009" "Neil Horman" +.SH NAME +dropwatch \- kernel dropped packet monitoring utility +.SH SYNOPSIS +\fBdropwatch\fP [\fB\-l\fP \fImethod\fP | \fBlist\fP] +.SH DESCRIPTION +.B dropwatch +is an interactive utility for monitoring and recording packets that +are dropped by the kernel. +.SH OPTIONS +.TP +\fB\-l\fP \fImethod\fP | \fBlist\fP, \fB\-\-lmethod\fP \fImethod\fP | \fBlist\fP +Select the translation method to use when a drop alert arrives. By default the +raw instruction pointer of a drop location is output, but by the use of the \fB\-l\fP +option, we can assign a translation method so that the instruction pointer can +be translated into function names. Currently supported lookup \fImethods\fP are: +.RS +.TP +.B list +Show all supported methods. +.TP +.B kas +Use \fI/proc/kallsyms\fP to lookup instruction pointers to function mappings. +.RE +.SH INTERACTIVE COMMANDS +.TP +.B start +Tells the kernel to start reporting dropped packets. +.TP +.B stop +Tells the kernel to discontinue reporting dropped packets. +.TP +.B exit +Exits the \fBdropmonitor\fP program. +.TP +.B help +Displays summary of all commands. +.TP +\fBset alertlimit\fP \fIvalue\fP +Sets a triggerpoint to stop monitoring for dropped packets after \fIvalue\fP alerts +have been received. +.TP +.BR "set alertmode " "{ " summary " | " packet " }" +.I summary +- Default mode. A summary of recent packet drops is sent to user space. The +alert includes drop locations and number of drops in each location. + +.I packet +- Each dropped packet is sent to user space. The alert includes the packet +itself and various metadata such as drop location and timestamp. +.TP +.BI "set trunc " "len" +Sets the truncation length. Reported packets will be truncated to length +\fIlen\fP. This setting is only applicable when \fIalertmode\fP is set to +\fIpacket\fP. By default packets are not truncated. A value of \fI0\fP disables +truncation. +.TP +.BI "set queue " "len" +Sets the queue length in the kernel. When \fIalertmode\fP is set to +\fIpacket\fP, the kernel queues dropped packets in a per-CPU drop list before +preparing a netlink message for each. This setting controls the queue's length. +By default this is limited by the kernel to 1,000 packets. Increasing this +value will increase the memory utilization of the system. +.TP +.BR "set sw " "{ " true " | " false " }" +Enables or disables the monitoring of software originated drops. By default +software originated drops are monitored. +.TP +.BR "set hw " "{ " true " | " false " }" +Enables or disables the monitoring of hardware originated drops. By default +hardware originated drops are not monitored. +.TP +.B show +Query existing configuration from the kernel and show it. +.TP +.B stats +Query statistics from the kernel and show it. + +.I "Tail dropped" +- Number of packets that could not be enqueued to the per-CPU drop list(s). diff --git a/doc/dwdump.1 b/doc/dwdump.1 new file mode 100644 index 0000000..afd3ffb --- /dev/null +++ b/doc/dwdump.1 @@ -0,0 +1,83 @@ +.TH dwdump 1 "Jan 2020" "Ido Schimmel" +.SH NAME +dwdump \- dump kernel dropped packets to a file +.SH SYNOPSIS +.sp +.ad l +.in +8 +.ti -8 +.B dwdump +.RI "[ " OPTIONS " ]" +.sp + +.SH OPTIONS + +.TP +.BR "\-w" , " --write " \fIFILE +Dump packets to provided file in pcap format. Defaults to standard output. + +.TP +.BR "\-t", " --trunc " \fILENGTH +Ask the kernel to truncate packets to provided length. Defaults to no +truncation. + +.TP +.BR "-q", " --query" +Query the kernel for current configuration and exit. + +.TP +.BR "-l", " --limit " \fILIMIT +Ask the kernel to set the per-CPU packet queue limit to provided limit. +Defaults to 1,000 packets. + +.TP +.BR "-p", " --passive" +Only listen on notified packets with no configuration. This is useful if the +kernel is already monitoring dropped packets and you only want to open another +listening socket. + +.TP +.BR "-s", " --stats" +Query the kernel for statistics and exit. + +.TP +.BR "-b", " --bufsize " \fISIZE +Set the socket's receive buffer to provided size. Defaults to 1MB. + +.TP +.BR "-o", " --origin " "{ " sw " | " hw " }" +Ask the kernel to only monitor software or hardware originated drops. Defaults +to both. See \fBdevlink-trap\fR(8) for details on how to get hardware +originated drops to the kernel. + +.TP +.BR "-e", " --exit" +Ask the kernel to stop monitoring and exit. + +.SH "EXAMPLES" +.PP +dwdump -w drops.pcap +.RS 4 +Dump dropped packets to a file. +.RE +.PP +dwdump | tshark -V -r - +.RS 4 +Pipe dropped packets to Wireshark. +.RE +.PP +dwdump -o sw -w drops.pcap +.RS 4 +Only monitor software originated drops. +.RE +.PP +dwdump -q +.RS 4 +Query current configuration from the kernel and exit. +.RE + +.SH SEE ALSO +.BR dropwatch (1), +.BR devlink-trap (8), +.BR tshark (1), +.br diff --git a/spec/dropwatch.spec b/spec/dropwatch.spec new file mode 100644 index 0000000..92cbb5b --- /dev/null +++ b/spec/dropwatch.spec @@ -0,0 +1,57 @@ +%define uversion MAKEFILE_VERSION +Summary: Kernel dropped packet monitor +Name: dropwatch +Version: %{uversion} +Release: 0%{?dist} +Source0: https://fedorahosted.org/releases/d/r/dropwatch/dropwatch-%{uversion}.tbz2 +URL: http://fedorahosted.org/dropwatch +License: GPLv2+ +Group: Applications/System +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRequires: kernel-devel, libnl-devel, readline-devel +BuildRequires: binutils-devel, binutils-static pkgconfig +Requires: libnl, readline + +%description +dropwatch is an utility to interface to the kernel to monitor for dropped +network packets. + +%prep +%setup -q + +%build +cd src +export CFLAGS=$RPM_OPT_FLAGS +make + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT%{_bindir} +mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 +install -m0755 src/dropwatch $RPM_BUILD_ROOT%{_bindir} +install -m0644 doc/dropwatch.1 $RPM_BUILD_ROOT%{_mandir}/man1 + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_mandir}/man1/* +%doc README +%doc COPYING + +%changelog +* Thu Apr 08 2010 Neil Horman 1.1-0 +- Fixing BuildRequires in spec, and removing release variable + +* Thu Mar 26 2009 Neil Horman 1.0-3 +- Updating Makefile to include release num in tarball + +* Fri Mar 20 2009 Neil Horman 1.0-2 +- Fixed up Errors found in package review (bz 491240) + +* Tue Mar 17 2009 Neil Horman 1.0-1 +- Initial build + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..a324fd3 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,14 @@ + +bin_PROGRAMS = dropwatch dwdump + +AM_CFLAGS = -g -Wall -Werror $(LIBNL3_CFLAGS) $(READLINE_CFLAGS) +AM_LDFLAGS = $(LIBNL3_LIBS) -lnl-genl-3 $(READLINE_LIBS) -lpcap +AM_CPPFLAGS = -D_GNU_SOURCE + +dropwatch_SOURCES = main.c lookup.c lookup_kas.c +dwdump_SOURCES = dwdump.c + +if USE_BFD +dropwatch_SOURCES += lookup_bfd.c +AM_LDFLAGS += -lbfd +endif diff --git a/src/dwdump.c b/src/dwdump.c new file mode 100644 index 0000000..2329868 --- /dev/null +++ b/src/dwdump.c @@ -0,0 +1,774 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net_dropmon.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +static struct nla_policy net_dm_policy[NET_DM_ATTR_MAX + 1] = { + [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, + [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, + [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, + [NET_DM_ATTR_STATS] = { .type = NLA_NESTED }, + [NET_DM_ATTR_HW_STATS] = { .type = NLA_NESTED }, +}; + +static struct nla_policy +net_dm_stats_policy[NET_DM_ATTR_STATS_MAX + 1] = { + [NET_DM_ATTR_STATS_DROPPED] = { .type = NLA_U64 }, +}; + +static bool stop; + +enum dwdump_pkt_origin { + DWDUMP_PKT_ORIGIN_ALL, + DWDUMP_PKT_ORIGIN_SW, + DWDUMP_PKT_ORIGIN_HW, +}; + +struct dwdump_options { + const char *dumpfile; + __u32 trunc_len; + bool query; + __u32 queue_len; + bool passive; + bool stats; + int rxbuf; + bool exit; + enum dwdump_pkt_origin origin; + bool need_pcap; + bool need_mon; +}; + +struct dwdump { + struct nl_sock *csk, *dsk; + pcap_t *pcap_handle; + pcap_dumper_t *pcap_dumper; + struct dwdump_options options; + int family; + long snaplen; + char *pcap_buf, *pkt; +}; + +/* Based on https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html */ +struct linux_sll { + __be16 pkttype; + __be16 hatype; + __be16 halen; + unsigned char addr[8]; + __be16 family; +}; + +static int dwdump_data_init(struct dwdump *dwdump) +{ + struct nl_sock *sk; + int family, err; + + sk = nl_socket_alloc(); + if (!sk) { + fprintf(stderr, "Failed to allocate data socket\n"); + return -1; + } + + /* Add wiggle room for other netlink attributes in addition to the + * payload. + */ + dwdump->snaplen = dwdump->options.trunc_len + 2048; + dwdump->pkt = calloc(1, dwdump->snaplen); + if (!dwdump->pkt) { + perror("calloc"); + goto err_pkt_alloc; + } + + err = genl_connect(sk); + if (err) { + fprintf(stderr, "Failed to connect data socket\n"); + goto err_genl_connect; + } + + family = genl_ctrl_resolve(sk, "NET_DM"); + if (family < 0) { + fprintf(stderr, "Failed to resolve ID of \"NET_DM\" family\n"); + goto err_genl_ctrl_resolve; + } + + err = nl_socket_set_buffer_size(sk, dwdump->options.rxbuf, 0); + if (err < 0) { + fprintf(stderr, "Failed to set receive buffer size of data socket\n"); + goto err_set_buffer_size; + } + + err = nl_socket_add_memberships(sk, NET_DM_GRP_ALERT, NFNLGRP_NONE); + if (err) { + fprintf(stderr, "Failed to join multicast group\n"); + goto err_add_memberships; + } + + dwdump->dsk = sk; + dwdump->family = family; + + return 0; + +err_add_memberships: +err_set_buffer_size: +err_genl_ctrl_resolve: +err_genl_connect: + free(dwdump->pkt); +err_pkt_alloc: + nl_socket_free(sk); + return -1; +} + +static void dwdump_data_fini(struct dwdump *dwdump) +{ + nl_socket_drop_memberships(dwdump->dsk, NET_DM_GRP_ALERT, NFNLGRP_NONE); + free(dwdump->pkt); + nl_socket_free(dwdump->dsk); +} + +static const char *dwdump_alert_mode(uint8_t alert_mode) +{ + switch (alert_mode) { + case NET_DM_ALERT_MODE_SUMMARY: + return "summary"; + case NET_DM_ALERT_MODE_PACKET: + return "packet"; + } + + return "invalid alert mode"; +} + +static int dwdump_config_set(struct dwdump *dwdump) +{ + struct nl_sock *sk = dwdump->csk; + struct nl_msg *msg; + int err; + + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "Failed to allocate netlink message\n"); + return -1; + } + + if (!genlmsg_put(msg, 0, NL_AUTO_SEQ, dwdump->family, 0, + NLM_F_REQUEST|NLM_F_ACK, NET_DM_CMD_CONFIG, 0)) + goto genlmsg_put_failure; + + if (nla_put_u8(msg, NET_DM_ATTR_ALERT_MODE, NET_DM_ALERT_MODE_PACKET)) + goto nla_put_failure; + + if (nla_put_u32(msg, NET_DM_ATTR_TRUNC_LEN, dwdump->options.trunc_len)) + goto nla_put_failure; + + if (nla_put_u32(msg, NET_DM_ATTR_QUEUE_LEN, dwdump->options.queue_len)) + goto nla_put_failure; + + err = nl_send_sync(sk, msg); + if (err < 0) { + fprintf(stderr, "Failed to configure drop monitor kernel module\n"); + return err; + } + + return 0; + +nla_put_failure: +genlmsg_put_failure: + nlmsg_free(msg); + return -EMSGSIZE; +} + +static int dwdump_config_get(struct dwdump *dwdump) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + struct sockaddr_nl nla; + unsigned char *buf; + uint8_t alert_mode; + int len, err; + + err = genl_send_simple(dwdump->csk, dwdump->family, + NET_DM_CMD_CONFIG_GET, 0, NLM_F_REQUEST); + if (err < 0) { + fprintf(stderr, "Failed to query configuration\n"); + return -1; + } + + len = nl_recv(dwdump->csk, &nla, &buf, NULL); + if (len < 0) + return -1; + + err = genlmsg_parse((void *) buf, 0, attrs, NET_DM_ATTR_MAX, + net_dm_policy); + if (err < 0) + return -1; + + if (!attrs[NET_DM_ATTR_ALERT_MODE] || !attrs[NET_DM_ATTR_TRUNC_LEN] || + !attrs[NET_DM_ATTR_QUEUE_LEN]) + return -1; + + alert_mode = nla_get_u8(attrs[NET_DM_ATTR_ALERT_MODE]); + printf("Alert mode: %s\n", dwdump_alert_mode(alert_mode)); + + printf("Truncation length: %u\n", + nla_get_u32(attrs[NET_DM_ATTR_TRUNC_LEN])); + + printf("Queue length: %u\n", nla_get_u32(attrs[NET_DM_ATTR_QUEUE_LEN])); + + return 0; +} + +static void dwdump_nested_stats_print(struct nlattr *attr) +{ + struct nlattr *attrs[NET_DM_ATTR_STATS_MAX + 1]; + int err; + + err = nla_parse_nested(attrs, NET_DM_ATTR_STATS_MAX, attr, + net_dm_stats_policy); + if (err) + return; + + if (attrs[NET_DM_ATTR_STATS_DROPPED]) + printf("Tail dropped: %" PRIu64 "\n", + nla_get_u64(attrs[NET_DM_ATTR_STATS_DROPPED])); +} + +static int dwdump_stats_get(struct dwdump *dwdump) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + struct sockaddr_nl nla; + unsigned char *buf; + int len, err; + + err = genl_send_simple(dwdump->csk, dwdump->family, + NET_DM_CMD_STATS_GET, 0, NLM_F_REQUEST); + if (err < 0) { + fprintf(stderr, "Failed to query statistics\n"); + return -1; + } + + len = nl_recv(dwdump->csk, &nla, &buf, NULL); + if (len < 0) + return -1; + + err = genlmsg_parse((void *) buf, 0, attrs, NET_DM_ATTR_MAX, + net_dm_policy); + if (err < 0) + return -1; + + if (attrs[NET_DM_ATTR_STATS]) { + printf("Software statistics:\n"); + dwdump_nested_stats_print(attrs[NET_DM_ATTR_STATS]); + } + + if (attrs[NET_DM_ATTR_HW_STATS]) { + printf("Hardware statistics:\n"); + dwdump_nested_stats_print(attrs[NET_DM_ATTR_HW_STATS]); + } + + return 0; +} + +static int dwdump_monitor_origin_put(const struct dwdump *dwdump, + struct nl_msg *msg) +{ + switch (dwdump->options.origin) { + case DWDUMP_PKT_ORIGIN_ALL: + if (nla_put_flag(msg, NET_DM_ATTR_SW_DROPS) || + nla_put_flag(msg, NET_DM_ATTR_HW_DROPS)) + return -EMSGSIZE; + break; + case DWDUMP_PKT_ORIGIN_SW: + if (nla_put_flag(msg, NET_DM_ATTR_SW_DROPS)) + return -EMSGSIZE; + break; + case DWDUMP_PKT_ORIGIN_HW: + if (nla_put_flag(msg, NET_DM_ATTR_HW_DROPS)) + return -EMSGSIZE; + break; + } + + return 0; +} + +static int dwdump_monitor(struct dwdump *dwdump, bool start) +{ + uint8_t cmd = start ? NET_DM_CMD_START : NET_DM_CMD_STOP; + struct nl_sock *sk = dwdump->csk; + struct nl_msg *msg; + int err; + + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "Failed to allocate netlink message\n"); + return -1; + } + + if (!genlmsg_put(msg, 0, NL_AUTO_SEQ, dwdump->family, 0, + NLM_F_REQUEST|NLM_F_ACK, cmd, 0)) + goto genlmsg_put_failure; + + err = dwdump_monitor_origin_put(dwdump, msg); + if (err < 0) + goto genlmsg_put_failure; + + err = nl_send_sync(sk, msg); + if (err < 0) { + fprintf(stderr, "Failed to %s monitoring\n", + start ? "start" : "stop"); + return err; + } + + return 0; + +genlmsg_put_failure: + nlmsg_free(msg); + return -EMSGSIZE; +} + +static void dwdump_handler(int sig) +{ + stop = true; +} + +static int dwdump_sighandler_install(void) +{ + static const int signals_catch[] = { + SIGINT, SIGQUIT, SIGTERM, SIGPIPE, SIGHUP, + }; + struct sigaction saction; + int i, err; + + memset(&saction, 0, sizeof(saction)); + saction.sa_handler = dwdump_handler; + + for (i = 0; i < ARRAY_SIZE(signals_catch); i++) { + err = sigaction(signals_catch[i], &saction, NULL); + if (err) { + perror("sigaction"); + return err; + } + } + + return 0; +} + +static int dwdump_ctrl_init(struct dwdump *dwdump) +{ + struct nl_sock *sk; + int err; + + sk = nl_socket_alloc(); + if (!sk) { + fprintf(stderr, "Failed to allocate control socket\n"); + return -1; + } + + err = genl_connect(sk); + if (err) { + fprintf(stderr, "Failed to connect control socket"); + goto err_genl_connect; + } + + dwdump->csk = sk; + + if (!dwdump->options.need_mon) + return 0; + + err = dwdump_config_set(dwdump); + if (err) + goto err_config_set; + + err = dwdump_monitor(dwdump, true); + if (err) + goto err_monitor; + + return 0; + +err_monitor: +err_config_set: +err_genl_connect: + nl_socket_free(sk); + return err; +} + +static void dwdump_ctrl_fini(struct dwdump *dwdump) +{ + if (!dwdump->options.need_mon) + goto out; + + dwdump_monitor(dwdump, false); +out: + nl_socket_free(dwdump->csk); +} + +static void dwdump_pcap_write(struct dwdump *dwdump, unsigned char *buf, + int len) +{ + struct pcap_pkthdr hdr; + int pkt_len; + + if (len + sizeof(struct linux_sll) < dwdump->snaplen) + pkt_len = len + sizeof(struct linux_sll); + else + pkt_len = dwdump->snaplen - sizeof(struct linux_sll); + + memcpy(dwdump->pcap_buf + sizeof(struct linux_sll), buf, pkt_len); + + hdr.caplen = pkt_len; + hdr.len = pkt_len; + gettimeofday(&hdr.ts, NULL); + + pcap_dump((unsigned char *) dwdump->pcap_dumper, &hdr, + (const unsigned char *) dwdump->pcap_buf); + /* In case packets are written to stdout, make sure each packet is + * immediately written and not buffered. + */ + fflush(NULL); +} + +static int dwdump_pcap_buf_init(struct dwdump *dwdump) +{ + struct linux_sll sll; + + /* The wireshark netlink dissector expects netlink messages to start + * with a Linux cooked header (SLL), so include it before each packet. + */ + memset(&sll, 0, sizeof(sll)); + sll.pkttype = htons(PACKET_OUTGOING); + sll.hatype = htons(ARPHRD_NETLINK); + sll.family = htons(AF_NETLINK); + + dwdump->pcap_buf = calloc(1, dwdump->snaplen); + if (!dwdump->pcap_buf) { + perror("calloc"); + return -1; + } + + memcpy(dwdump->pcap_buf, &sll, sizeof(sll)); + + return 0; +} + +static void dwdump_pcap_buf_fini(struct dwdump *dwdump) +{ + free(dwdump->pcap_buf); +} + +static int dwdump_pcap_genl_init(struct dwdump *dwdump) +{ + struct sockaddr_nl nla; + unsigned char *buf; + int len, err; + + /* In order for wireshark to be able to invoke the net_dm dissector, + * it must learn about the mapping between the generic netlink + * family ID and its name from this dump. + * + * Reference: + * https://www.wireshark.org/lists/wireshark-users/201907/msg00027.html + */ + err = genl_send_simple(dwdump->csk, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, + 1, NLM_F_DUMP); + if (err < 0) { + fprintf(stderr, "Failed to dump generic netlink families\n"); + return -1; + } + + len = nl_recv(dwdump->csk, &nla, &buf, NULL); + if (len < 0) + return -1; + + dwdump_pcap_write(dwdump, buf, len); + + return 0; +} + +static int dwdump_pcap_init(struct dwdump *dwdump) +{ + int err; + + if (!dwdump->options.need_pcap) + return 0; + + dwdump->pcap_handle = pcap_open_dead(DLT_NETLINK, dwdump->snaplen); + if (!dwdump->pcap_handle) { + perror("pcap_open_dead"); + return -1; + } + + dwdump->pcap_dumper = pcap_dump_open(dwdump->pcap_handle, + dwdump->options.dumpfile); + if (!dwdump->pcap_dumper) { + pcap_perror(dwdump->pcap_handle, "pcap_dump_open"); + goto err_dump_open; + } + + err = dwdump_pcap_buf_init(dwdump); + if (err) + goto err_buf_init; + + err = dwdump_pcap_genl_init(dwdump); + if (err) + goto err_genl_init; + + return 0; + +err_genl_init: + dwdump_pcap_buf_fini(dwdump); +err_buf_init: + pcap_dump_close(dwdump->pcap_dumper); +err_dump_open: + pcap_close(dwdump->pcap_handle); + return -1; +} + +static void dwdump_pcap_fini(struct dwdump *dwdump) +{ + if (!dwdump->options.need_pcap) + return; + + dwdump_pcap_buf_fini(dwdump); + pcap_dump_close(dwdump->pcap_dumper); + pcap_close(dwdump->pcap_handle); +} + +static int dwdump_init(struct dwdump *dwdump) +{ + int err; + + err = dwdump_data_init(dwdump); + if (err) + return err; + + err = dwdump_ctrl_init(dwdump); + if (err) + goto err_ctrl_init; + + err = dwdump_pcap_init(dwdump); + if (err) + goto err_pcap_init; + + err = dwdump_sighandler_install(); + if (err) + goto err_sighandler_install; + + return 0; + +err_sighandler_install: + dwdump_pcap_fini(dwdump); +err_pcap_init: + dwdump_ctrl_fini(dwdump); +err_ctrl_init: + dwdump_data_fini(dwdump); + return err; +} + +static void dwdump_fini(struct dwdump *dwdump) +{ + dwdump_pcap_fini(dwdump); + dwdump_ctrl_fini(dwdump); + dwdump_data_fini(dwdump); +} + +static int dwdump_main(struct dwdump *dwdump) +{ + int fd = nl_socket_get_fd(dwdump->dsk); + + if (dwdump->options.query) + return dwdump_config_get(dwdump); + + if (dwdump->options.stats) + return dwdump_stats_get(dwdump); + + if (dwdump->options.exit) + return dwdump_monitor(dwdump, false); + + while (!stop) { + int len; + + /* Use recv() instead of nl_recv() since interruption of + * nl_recv() causes the operation to be retried. + */ + len = recv(fd, dwdump->pkt, dwdump->snaplen, 0); + if (len < 0) { + switch (errno) { + case EINTR: /* fall-through */ + case ENOBUFS: + continue; + default: + perror("recv"); + return -1; + } + } + + dwdump_pcap_write(dwdump, (unsigned char *) dwdump->pkt, len); + } + + return 0; +} + +static int dwdump_origin_parse(struct dwdump *dwdump, const char *origin) +{ + if (strcmp(origin, "sw") == 0) { + dwdump->options.origin = DWDUMP_PKT_ORIGIN_SW; + return 0; + } else if (strcmp(origin, "hw") == 0) { + dwdump->options.origin = DWDUMP_PKT_ORIGIN_HW; + return 0; + } else { + fprintf(stderr, "Invalid origin: \'%s\'\n", origin); + return -EINVAL; + } +} + +static void dwdump_usage(FILE *fp) +{ + fprintf(fp, "Usage:\n"); + fprintf(fp, "dwdump [ -w -t -q -l -p -s -b -e -o ]\n"); + fprintf(fp, " -w dump packets to provided file. defaults to standard output\n"); + fprintf(fp, " -t truncate packets to provided length. defaults to no truncation\n"); + fprintf(fp, " -q query the kernel for current configuration and exit\n"); + fprintf(fp, " -l set packet queue limit to provided limit\n"); + fprintf(fp, " -p only listen on notified packets with no configuration\n"); + fprintf(fp, " -s query kernel for statistics and exit\n"); + fprintf(fp, " -b set the socket's receive buffer to provided size\n"); + fprintf(fp, " -o monitor only originated drops. defaults to all\n"); + fprintf(fp, " -e ask kernel to stop monitoring and exit\n"); +} + +static int dwdump_opts_parse(struct dwdump *dwdump, int argc, char **argv) +{ + static const struct option long_options[] = { + { "write", required_argument, NULL, 'w' }, + { "trunc", required_argument, NULL, 't' }, + { "query", no_argument, NULL, 'q' }, + { "limit", required_argument, NULL, 'l' }, + { "passive", no_argument, NULL, 'p' }, + { "stats", no_argument, NULL, 's' }, + { "bufsize", required_argument, NULL, 'b' }, + { "origin", required_argument, NULL, 'o' }, + { "exit", no_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + static const char optstring[] = "w:t:ql:psb:o:eh"; + int opt, err; + + /* Default values */ + dwdump->options.dumpfile = "/dev/stdout"; + dwdump->options.trunc_len = 0xffff; + dwdump->options.queue_len = 1000; + dwdump->options.rxbuf = 1024 * 1024; + dwdump->options.origin = DWDUMP_PKT_ORIGIN_ALL; + dwdump->options.need_pcap = true; + dwdump->options.need_mon = true; + + while ((opt = getopt_long(argc, argv, optstring, + long_options, NULL)) != -1) { + switch (opt) { + case 'w': + dwdump->options.dumpfile = optarg; + break; + case 't': + dwdump->options.trunc_len = atol(optarg); + if (dwdump->options.trunc_len == 0 || + dwdump->options.trunc_len > 0xffff) + dwdump->options.trunc_len = 0xffff; + break; + case 'q': + dwdump->options.query = true; + dwdump->options.need_pcap = false; + dwdump->options.need_mon = false; + break; + case 'l': + dwdump->options.queue_len = atol(optarg); + break; + case 'p': + dwdump->options.passive = true; + dwdump->options.need_mon = false; + break; + case 's': + dwdump->options.stats = true; + dwdump->options.need_pcap = false; + dwdump->options.need_mon = false; + break; + case 'b': + dwdump->options.rxbuf = atol(optarg); + break; + case 'o': + err = dwdump_origin_parse(dwdump, optarg); + if (err) + return err; + break; + case 'e': + dwdump->options.exit = true; + dwdump->options.need_pcap = false; + dwdump->options.need_mon = false; + break; + case 'h': + dwdump_usage(stdout); + return -1; + case '?': + dwdump_usage(stderr); + return -1; + default: + fprintf(stderr, "Unknown option: \'%c\'\n", opt); + dwdump_usage(stderr); + return -1; + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct dwdump *dwdump; + int err; + + dwdump = calloc(1, sizeof(*dwdump)); + if (!dwdump) { + perror("calloc"); + goto err_dwdump_alloc; + } + + err = dwdump_opts_parse(dwdump, argc, argv); + if (err) + goto err_opts_parse; + + err = dwdump_init(dwdump); + if (err) + goto err_dwdump_init; + + err = dwdump_main(dwdump); + + dwdump_fini(dwdump); + free(dwdump); + + return err; + +err_dwdump_init: +err_opts_parse: + free(dwdump); +err_dwdump_alloc: + exit(EXIT_FAILURE); +} diff --git a/src/lookup.c b/src/lookup.c new file mode 100644 index 0000000..714cf0f --- /dev/null +++ b/src/lookup.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2009, Neil Horman + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * This is a translator. given an input address, this will convert it into a + * function and offset. Unless overridden, it will automatically determine + * translations using the following methods, in order of priority: + * 1) /usr/lib/debug/ using libbfd + * 2) /proc/kallsyms + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_BFD_H +#include +#endif +#include +#include +#include +#include + +#include "lookup.h" + +#ifdef HAVE_BFD_H +extern struct lookup_methods bfd_methods; +#endif +extern struct lookup_methods kallsym_methods; + +static int lookup_null_init(void) +{ + printf("Initializing null lookup method\n"); + return 0; +} + +static int lookup_null_sym(void *pc, struct loc_result *location) +{ + /* + * In the null method, every lookup fails + */ + return 1; +} + +static struct lookup_methods null_methods = { + lookup_null_init, + lookup_null_sym, +}; + +static struct lookup_methods *methods = NULL; + +int init_lookup(lookup_init_method_t method) +{ + int rc; + switch (method) { + case METHOD_NULL: + /* + * Don't actually do any lookups, + * just pretend everything is + * not found + */ + methods = &null_methods; + break; + case METHOD_AUTO: +#ifdef HAVE_BFD_H + methods = &bfd_methods; + if (methods->lookup_init() == 0) + return 0; +#endif + methods = &kallsym_methods; + if (methods->lookup_init() == 0) + return 0; + methods = NULL; + return -1; +#ifdef HAVE_BFD_H + case METHOD_DEBUGINFO: + methods = &bfd_methods; + break; +#endif + case METHOD_KALLSYMS: + methods = &kallsym_methods; + break; + } + + rc = methods->lookup_init(); + if (rc < 0) + methods = NULL; + return rc; +} + +int lookup_symbol(void *pc, struct loc_result *loc) +{ + if (loc == NULL) + return 1; + return methods->get_symbol(pc, loc); +} diff --git a/src/lookup.h b/src/lookup.h new file mode 100644 index 0000000..e5b6d3f --- /dev/null +++ b/src/lookup.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009, Neil Horman + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * This is a translator. given an input address, this will convert it into a + * function and offset. Unless overridden, it will automatically determine + * translations using the following methods, in order of priority: + * 1) /usr/lib/debug/ using libbfd + * 2) /proc/kallsyms + */ + +#include "config.h" + +#include +#include + + +/* + * Initialization routine + * INPUTS: + * method - enum describing how to do translation + * * METHOD_NULL : Just print pc values, not symbols + * * METHOD_AUTO : automatic search for best method + * * METHOD_DEBUGINFO : use debuginfo package + * * METHOD_KALLSYMS : use /proc/kallsyms + * returns: + * * 0 : initalization succeeded + * * < 0 : initalization failed + */ +typedef enum { + METHOD_NULL = 0, + METHOD_AUTO, +#ifdef HAVE_BFD_H + METHOD_DEBUGINFO, +#endif + METHOD_KALLSYMS +} lookup_init_method_t; + +struct loc_result { + const char *symbol; + __u64 offset; +}; + +int init_lookup(lookup_init_method_t method); +int lookup_symbol(void *pc, struct loc_result *location); + +struct lookup_methods { + int (*lookup_init)(void); + int(*get_symbol)(void *pc, struct loc_result *location); +}; + + diff --git a/src/lookup_bfd.c b/src/lookup_bfd.c new file mode 100644 index 0000000..8fa4833 --- /dev/null +++ b/src/lookup_bfd.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009, Neil Horman + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * This is a translator. given an input address, this will convert it into a + * symbollic name using the bfd library + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lookup.h" + + +static int lookup_bfd_init(void) +{ + struct utsname uts; + struct stat sb; + char *dbibuf; + + /* + *Start by determining if we have the required debuginfo package + *here + */ + if(uname(&uts)<0) + return-1; + + dbibuf = malloc(strlen("/usr/lib/debug/lib/modules") + strlen(uts.release) + 1); + sprintf(dbibuf,"/usr/lib/debug/lib/modules/%s", uts.release); + if (stat(dbibuf,&sb) < 0) { + free(dbibuf); + goto out_fail; + } + + free(dbibuf); + + + bfd_init(); + return 0; + +out_fail: + return-1; +} + +static int lookup_bfd_sym(void *pc, struct loc_result *location) +{ + return 1; +} + +struct lookup_methods bfd_methods = { + lookup_bfd_init, + lookup_bfd_sym, +}; diff --git a/src/lookup_kas.c b/src/lookup_kas.c new file mode 100644 index 0000000..7e41e78 --- /dev/null +++ b/src/lookup_kas.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2009, Neil Horman + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * This is a translator. given an input address, this will convert it into a + * symbolic name using /proc/kallsyms + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lookup.h" + +struct symbol_entry { + char *sym_name; + __u64 start; + __u64 end; + LIST_ENTRY(symbol_entry) list; +}; + +LIST_HEAD(sym_list, symbol_entry); + +/* + * This is our cache of symbols that we've previously looked up + */ +static struct sym_list sym_list_head = {NULL}; + + +static int lookup_kas_cache( __u64 pc, struct loc_result *location) +{ + struct symbol_entry *sym; + + LIST_FOREACH(sym, &sym_list_head, list) { + if ((pc >= sym->start) && + (pc <= sym->end)) { + location->symbol = sym->sym_name; + location->offset = (pc - sym->start); + return 0; + } + } + + return 1; +} + +static void kas_add_cache(__u64 start, __u64 end, char *name) +{ + struct symbol_entry *sym = NULL; + + sym = malloc(sizeof(struct symbol_entry)); + if (!sym) + return; + + sym->start = start; + sym->end = end; + sym->sym_name = name; + + LIST_INSERT_HEAD(&sym_list_head, sym, list); + return; +} + +static int lookup_kas_proc(__u64 pc, struct loc_result *location) +{ + FILE *pf; + __u64 ppc; + __u64 uppc, ulpc, uipc; + char *name, *last_name; + + pf = fopen("/proc/kallsyms", "r"); + + if (!pf) + return 1; + + last_name = NULL; + uipc = pc; + ulpc = 0; + while (!feof(pf)) { + /* + * Each line of /proc/kallsyms is formatteded as: + * - "%pK %c %s\n" (for kernel internal symbols), or + * - "%pK %c %s\t[%s]\n" (for module-provided symbols) + */ + if (fscanf(pf, "%llx %*s %ms [ %*[^]] ]", (unsigned long long *)&ppc, &name) < 0) { + perror("Error Scanning File: "); + break; + } + + uppc = (__u64)ppc; + if ((uipc >= ulpc) && + (uipc < uppc)) { + /* + * The last symbol we looked at + * was a hit, record and return it + * Note that we don't free last_name + * here, because the cache is using it + */ + kas_add_cache(ulpc, uppc-1, last_name); + fclose(pf); + free(name); + return lookup_kas_cache(pc, location); + } + + /* + * Advance all our state holders + */ + free(last_name); + last_name = name; + ulpc = uppc; + } + + fclose(pf); + return 1; +} + +static int lookup_kas_init(void) +{ + printf("Initializing kallsyms db\n"); + + return 0; +} + +static int lookup_kas_sym(void *pc, struct loc_result *location) +{ + __u64 pcv; + + pcv = (uintptr_t)pc; + + if (!lookup_kas_cache(pcv, location)) + return 0; + + return lookup_kas_proc(pcv, location); +} + +struct lookup_methods kallsym_methods = { + lookup_kas_init, + lookup_kas_sym, +}; diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bd87085 --- /dev/null +++ b/src/main.c @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 2009, Neil Horman + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Opens our netlink socket. Returns the socket descriptor or < 0 on error + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net_dropmon.h" +#include "lookup.h" + +/* + * This is just in place until the kernel changes get committed + */ +#ifndef NETLINK_DRPMON +#define NETLINK_DRPMON 20 +#endif + +struct netlink_message { + void *msg; + struct nl_msg *nlbuf; + int refcnt; + LIST_ENTRY(netlink_message) ack_list_element; + int seq; + void (*ack_cb)(struct netlink_message *amsg, struct netlink_message *msg, int err); +}; + +LIST_HEAD(ack_list, netlink_message); + +struct ack_list ack_list_head = {NULL}; + +unsigned long alimit = 0; +unsigned long acount = 0; +unsigned long trunc_len = 0; +unsigned long queue_len = 0; +bool monitor_sw = false; +bool monitor_hw = false; + +void handle_dm_alert_msg(struct netlink_message *msg, int err); +void handle_dm_packet_alert_msg(struct netlink_message *msg, int err); +void handle_dm_config_new_msg(struct netlink_message *msg, int err); +void handle_dm_stats_new_msg(struct netlink_message *msg, int err); +void handle_dm_config_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); +void handle_dm_start_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); +void handle_dm_stop_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); +int disable_drop_monitor(); + +static void(*type_cb[_NET_DM_CMD_MAX])(struct netlink_message *, int err) = { + NULL, + handle_dm_alert_msg, + NULL, + NULL, + NULL, + handle_dm_packet_alert_msg, + NULL, + handle_dm_config_new_msg, + NULL, + handle_dm_stats_new_msg, +}; + +static struct nl_sock *nsd; +static int nsf; + +enum { + STATE_IDLE = 0, + STATE_ACTIVATING, + STATE_RECEIVING, + STATE_RQST_DEACTIVATE, + STATE_RQST_ACTIVATE, + STATE_DEACTIVATING, + STATE_FAILED, + STATE_EXIT, + STATE_RQST_ALERT_MODE_SUMMARY, + STATE_RQST_ALERT_MODE_PACKET, + STATE_ALERT_MODE_SETTING, + STATE_RQST_TRUNC_LEN, + STATE_TRUNC_LEN_SETTING, + STATE_RQST_QUEUE_LEN, + STATE_QUEUE_LEN_SETTING, + STATE_RQST_CONFIG, + STATE_CONFIG_GETTING, + STATE_RQST_STATS, + STATE_STATS_GETTING, +}; + +static int state = STATE_IDLE; + +static struct nla_policy net_dm_policy[NET_DM_ATTR_MAX + 1] = { + [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, + [NET_DM_ATTR_PC] = { .type = NLA_U64 }, + [NET_DM_ATTR_SYMBOL] = { .type = NLA_STRING }, + [NET_DM_ATTR_IN_PORT] = { .type = NLA_NESTED }, + [NET_DM_ATTR_TIMESTAMP] = { .type = NLA_U64 }, + [NET_DM_ATTR_PROTO] = { .type = NLA_U16 }, + [NET_DM_ATTR_PAYLOAD] = { .type = NLA_UNSPEC }, + [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, + [NET_DM_ATTR_ORIG_LEN] = { .type = NLA_U32 }, + [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, + [NET_DM_ATTR_STATS] = { .type = NLA_NESTED }, + [NET_DM_ATTR_HW_STATS] = { .type = NLA_NESTED }, + [NET_DM_ATTR_ORIGIN] = { .type = NLA_U16 }, + [NET_DM_ATTR_HW_TRAP_GROUP_NAME] = { .type = NLA_STRING }, + [NET_DM_ATTR_HW_TRAP_NAME] = { .type = NLA_STRING }, + [NET_DM_ATTR_HW_ENTRIES] = { .type = NLA_NESTED }, + [NET_DM_ATTR_HW_ENTRY] = { .type = NLA_NESTED }, + [NET_DM_ATTR_HW_TRAP_COUNT] = { .type = NLA_U32 }, +}; + +static struct nla_policy net_dm_port_policy[NET_DM_ATTR_PORT_MAX + 1] = { + [NET_DM_ATTR_PORT_NETDEV_IFINDEX] = { .type = NLA_U32 }, + [NET_DM_ATTR_PORT_NETDEV_NAME] = { .type = NLA_STRING }, +}; + +static struct nla_policy net_dm_stats_policy[NET_DM_ATTR_STATS_MAX + 1] = { + [NET_DM_ATTR_STATS_DROPPED] = { .type = NLA_U64 }, +}; + +int strtobool(const char *str, bool *p_val) +{ + bool val; + + if (!strcmp(str, "true") || !strcmp(str, "1")) + val = true; + else if (!strcmp(str, "false") || !strcmp(str, "0")) + val = false; + else + return -EINVAL; + *p_val = val; + return 0; +} + +void sigint_handler(int signum) +{ + if ((state == STATE_RECEIVING) || + (state == STATE_RQST_DEACTIVATE)) { + disable_drop_monitor(); + state = STATE_DEACTIVATING; + } else { + printf("Got a sigint while not receiving\n"); + } + return; +} + +struct nl_sock *setup_netlink_socket() +{ + struct nl_sock *sd; + int family; + + sd = nl_socket_alloc(); + + genl_connect(sd); + + family = genl_ctrl_resolve(sd, "NET_DM"); + + if (family < 0) { + printf("Unable to find NET_DM family, dropwatch can't work\n"); + goto out_close; + } + + nsf = family; + + nl_close(sd); + nl_socket_free(sd); + + sd = nl_socket_alloc(); + nl_join_groups(sd, NET_DM_GRP_ALERT); + + nl_connect(sd, NETLINK_GENERIC); + + return sd; + +out_close: + nl_close(sd); + nl_socket_free(sd); + return NULL; +} + +struct netlink_message *alloc_netlink_msg(uint32_t type, uint16_t flags, size_t size) +{ + struct netlink_message *msg; + static uint32_t seq = 0; + + msg = (struct netlink_message *)malloc(sizeof(struct netlink_message)); + + if (!msg) + return NULL; + + msg->refcnt = 1; + msg->nlbuf = nlmsg_alloc(); + msg->msg = genlmsg_put(msg->nlbuf, 0, seq, nsf, size, flags, type, 1); + + msg->ack_cb = NULL; + msg->seq = seq++; + + return msg; +} + +void set_ack_cb(struct netlink_message *msg, + void (*cb)(struct netlink_message *, struct netlink_message *, int)) +{ + if (msg->ack_cb) + return; + + msg->ack_cb = cb; + msg->refcnt++; + LIST_INSERT_HEAD(&ack_list_head, msg, ack_list_element); +} + +struct netlink_message *wrap_netlink_msg(struct nlmsghdr *buf) +{ + struct netlink_message *msg; + + msg = (struct netlink_message *)malloc(sizeof(struct netlink_message)); + if (msg) { + msg->refcnt = 1; + msg->msg = buf; + msg->nlbuf = NULL; + } + + return msg; +} + +int free_netlink_msg(struct netlink_message *msg) +{ + int refcnt; + + msg->refcnt--; + + refcnt = msg->refcnt; + + if (!refcnt) { + if (msg->nlbuf) + nlmsg_free(msg->nlbuf); + else + free(msg->msg); + free(msg); + } + + return refcnt; +} + +int send_netlink_message(struct netlink_message *msg) +{ + return nl_send(nsd, msg->nlbuf); +} + +struct netlink_message *recv_netlink_message(int *err) +{ + static unsigned char *buf; + struct netlink_message *msg; + struct genlmsghdr *glm; + struct sockaddr_nl nla; + int type; + int rc; + + *err = 0; + + do { + rc = nl_recv(nsd, &nla, &buf, NULL); + if (rc < 0) { + switch (errno) { + case EINTR: + /* + * Take a pass through the state loop + */ + return NULL; + break; + default: + perror("Receive operation failed:"); + return NULL; + break; + } + } + } while (rc == 0); + + msg = wrap_netlink_msg((struct nlmsghdr *)buf); + + type = ((struct nlmsghdr *)msg->msg)->nlmsg_type; + + /* + * Note the NLMSG_ERROR is overloaded + * Its also used to deliver ACKs + */ + if (type == NLMSG_ERROR) { + struct netlink_message *am; + struct nlmsgerr *errm = nlmsg_data(msg->msg); + LIST_FOREACH(am, &ack_list_head, ack_list_element) { + if (am->seq == errm->msg.nlmsg_seq) + break; + } + + if (am) { + LIST_REMOVE(am, ack_list_element); + am->ack_cb(msg, am, errm->error); + free_netlink_msg(am); + } else { + printf("Got an unexpected ack for sequence %d\n", errm->msg.nlmsg_seq); + } + + free_netlink_msg(msg); + return NULL; + } + + glm = nlmsg_data(msg->msg); + type = glm->cmd; + + if ((type > NET_DM_CMD_MAX) || + (type <= NET_DM_CMD_UNSPEC)) { + printf("Received message of unknown type %d\n", + type); + free_netlink_msg(msg); + return NULL; + } + + return msg; +} + +void process_rx_message(void) +{ + struct netlink_message *msg; + int err; + int type; + sigset_t bs; + + sigemptyset(&bs); + sigaddset(&bs, SIGINT); + sigprocmask(SIG_UNBLOCK, &bs, NULL); + msg = recv_netlink_message(&err); + sigprocmask(SIG_BLOCK, &bs, NULL); + + if (msg) { + struct nlmsghdr *nlh = msg->msg; + struct genlmsghdr *glh = nlmsg_data(nlh); + type = glh->cmd; + type_cb[type](msg, err); + } + return; +} + +void print_nested_hw_entry(struct nlattr *hw_entry) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + int err; + + err = nla_parse_nested(attrs, NET_DM_ATTR_MAX, hw_entry, net_dm_policy); + if (err) + return; + + if (!attrs[NET_DM_ATTR_HW_TRAP_NAME] || + !attrs[NET_DM_ATTR_HW_TRAP_COUNT]) + return; + + printf("%d drops at %s [hardware]\n", + nla_get_u32(attrs[NET_DM_ATTR_HW_TRAP_COUNT]), + nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_NAME])); +} + +void print_nested_hw_entries(struct nlattr *hw_entries) +{ + struct nlattr *attr; + int rem; + + nla_for_each_nested(attr, hw_entries, rem) { + if (nla_type(attr) != NET_DM_ATTR_HW_ENTRY) + continue; + print_nested_hw_entry(attr); + + acount++; + if (alimit && (acount == alimit)) { + printf("Alert limit reached, deactivating!\n"); + state = STATE_RQST_DEACTIVATE; + } + } +} + +/* + * These are the received message handlers + */ +void handle_dm_alert_msg(struct netlink_message *msg, int err) +{ + int i; + struct nlmsghdr *nlh = msg->msg; + struct genlmsghdr *glh = nlmsg_data(nlh); + struct loc_result res; + struct net_dm_alert_msg *alert = nla_data(genlmsg_data(glh)); + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + + if (state != STATE_RECEIVING) + goto out_free; + + err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); + if (err) + goto out_free; + + for (i=0; i < alert->entries; i++) { + void *location; + memcpy(&location, alert->points[i].pc, sizeof(void *)); + if (lookup_symbol(location, &res)) + printf ("%d drops at location %p [software]\n", alert->points[i].count, location); + else + printf ("%d drops at %s+%llx (%p) [software]\n", + alert->points[i].count, res.symbol, (unsigned long long)res.offset, location); + acount++; + if (alimit && (acount == alimit)) { + printf("Alert limit reached, deactivating!\n"); + state = STATE_RQST_DEACTIVATE; + } + } + + if (attrs[NET_DM_ATTR_HW_ENTRIES]) + print_nested_hw_entries(attrs[NET_DM_ATTR_HW_ENTRIES]); + +out_free: + free_netlink_msg(msg); +} + +void print_nested_port(struct nlattr *attr, const char *dir) +{ + struct nlattr *attrs[NET_DM_ATTR_PORT_MAX + 1]; + int err; + + err = nla_parse_nested(attrs, NET_DM_ATTR_PORT_MAX, attr, + net_dm_port_policy); + if (err) + return; + + if (attrs[NET_DM_ATTR_PORT_NETDEV_IFINDEX]) + printf("%s port ifindex: %d\n", dir, + nla_get_u32(attrs[NET_DM_ATTR_PORT_NETDEV_IFINDEX])); + + if (attrs[NET_DM_ATTR_PORT_NETDEV_NAME]) + printf("%s port name: %s\n", dir, + nla_get_string(attrs[NET_DM_ATTR_PORT_NETDEV_NAME])); +} + +void print_packet_origin(struct nlattr *attr) +{ + const char *origin; + uint16_t val; + + val = nla_get_u16(attr); + switch (val) { + case NET_DM_ORIGIN_SW: + origin = "software"; + break; + case NET_DM_ORIGIN_HW: + origin = "hardware"; + break; + default: + origin = "unknown"; + break; + } + + printf("origin: %s\n", origin); +} + +void handle_dm_packet_alert_msg(struct netlink_message *msg, int err) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + + if (state != STATE_RECEIVING) + goto out_free; + + err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); + if (err) + goto out_free; + + if (attrs[NET_DM_ATTR_PC] && attrs[NET_DM_ATTR_SYMBOL]) + printf("drop at: %s (0x%" PRIx64 ")\n", + nla_get_string(attrs[NET_DM_ATTR_SYMBOL]), + nla_get_u64(attrs[NET_DM_ATTR_PC])); + else if (attrs[NET_DM_ATTR_HW_TRAP_GROUP_NAME] && + attrs[NET_DM_ATTR_HW_TRAP_NAME]) + printf("drop at: %s (%s)\n", + nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_NAME]), + nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_GROUP_NAME])); + + if (attrs[NET_DM_ATTR_ORIGIN]) + print_packet_origin(attrs[NET_DM_ATTR_ORIGIN]); + + if (attrs[NET_DM_ATTR_IN_PORT]) + print_nested_port(attrs[NET_DM_ATTR_IN_PORT], "input"); + + if (attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]) { + unsigned char *cookie = nla_data(attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]); + int cookie_len = nla_len(attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]); + int i; + + printf("cookie: "); + for (i = 0; i < cookie_len; i++) + printf("%02x", cookie[i]); + printf("\n"); + } + + if (attrs[NET_DM_ATTR_TIMESTAMP]) { + time_t tv_sec; + struct tm *tm; + uint64_t ts; + char *tstr; + + ts = nla_get_u64(attrs[NET_DM_ATTR_TIMESTAMP]); + tv_sec = ts / 1000000000; + tm = localtime(&tv_sec); + + tstr = asctime(tm); + tstr[strlen(tstr) - 1] = 0; + printf("timestamp: %s %09" PRId64 " nsec\n", tstr, ts % 1000000000); + } + + if (attrs[NET_DM_ATTR_PROTO]) + printf("protocol: 0x%x\n", + nla_get_u16(attrs[NET_DM_ATTR_PROTO])); + + if (attrs[NET_DM_ATTR_PAYLOAD]) + printf("length: %u\n", nla_len(attrs[NET_DM_ATTR_PAYLOAD])); + + if (attrs[NET_DM_ATTR_ORIG_LEN]) + printf("original length: %u\n", + nla_get_u32(attrs[NET_DM_ATTR_ORIG_LEN])); + + printf("\n"); + + acount++; + if (alimit && (acount == alimit)) { + printf("Alert limit reached, deactivating!\n"); + state = STATE_RQST_DEACTIVATE; + } + +out_free: + free_netlink_msg(msg); +} + +void handle_dm_config_new_msg(struct netlink_message *msg, int err) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + + if (state != STATE_CONFIG_GETTING) + goto out_free; + + err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); + if (err) + goto out_free; + + if (!attrs[NET_DM_ATTR_ALERT_MODE] || !attrs[NET_DM_ATTR_TRUNC_LEN] || + !attrs[NET_DM_ATTR_QUEUE_LEN]) + goto out_free; + + printf("Alert mode: "); + switch (nla_get_u8(attrs[NET_DM_ATTR_ALERT_MODE])) { + case NET_DM_ALERT_MODE_SUMMARY: + printf("Summary\n"); + break; + case NET_DM_ALERT_MODE_PACKET: + printf("Packet\n"); + break; + default: + printf("Invalid alert mode\n"); + break; + } + + printf("Truncation length: %u\n", + nla_get_u32(attrs[NET_DM_ATTR_TRUNC_LEN])); + + printf("Queue length: %u\n", nla_get_u32(attrs[NET_DM_ATTR_QUEUE_LEN])); + +out_free: + state = STATE_IDLE; + free_netlink_msg(msg); +} + +void print_nested_stats(struct nlattr *attr) +{ + struct nlattr *attrs[NET_DM_ATTR_STATS_MAX + 1]; + int err; + + err = nla_parse_nested(attrs, NET_DM_ATTR_STATS_MAX, attr, + net_dm_stats_policy); + if (err) + return; + + if (attrs[NET_DM_ATTR_STATS_DROPPED]) + printf("Tail dropped: %" PRIu64 "\n", + nla_get_u64(attrs[NET_DM_ATTR_STATS_DROPPED])); +} + +void handle_dm_stats_new_msg(struct netlink_message *msg, int err) +{ + struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; + + if (state != STATE_STATS_GETTING) + goto out_free; + + err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); + if (err) + goto out_free; + + if (attrs[NET_DM_ATTR_STATS]) { + printf("Software statistics:\n"); + print_nested_stats(attrs[NET_DM_ATTR_STATS]); + } + + if (attrs[NET_DM_ATTR_HW_STATS]) { + printf("Hardware statistics:\n"); + print_nested_stats(attrs[NET_DM_ATTR_HW_STATS]); + } + +out_free: + state = STATE_IDLE; + free_netlink_msg(msg); +} + +void handle_dm_config_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) +{ + if (err != 0) { + char *erm = strerror(-err); + + printf("Failed config request, error: %s\n", erm); + state = STATE_FAILED; + return; + } + + switch (state) { + case STATE_ALERT_MODE_SETTING: + printf("Alert mode successfully set\n"); + state = STATE_IDLE; + break; + case STATE_TRUNC_LEN_SETTING: + printf("Truncation length successfully set\n"); + state = STATE_IDLE; + break; + case STATE_QUEUE_LEN_SETTING: + printf("Queue length successfully set\n"); + state = STATE_IDLE; + break; + default: + printf("Received acknowledgement for non-solicited config request\n"); + state = STATE_FAILED; + } +} + +void handle_dm_start_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) +{ + if (err != 0) { + char *erm = strerror(err*-1); + printf("Failed activation request, error: %s\n", erm); + state = STATE_FAILED; + goto out; + } + + if (state == STATE_ACTIVATING) { + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = sigint_handler; + act.sa_flags = SA_RESETHAND; + + printf("Kernel monitoring activated.\n"); + printf("Issue Ctrl-C to stop monitoring\n"); + sigaction(SIGINT, &act, NULL); + + state = STATE_RECEIVING; + } else { + printf("Odd, the kernel told us that it activated and we didn't ask\n"); + state = STATE_FAILED; + } +out: + return; +} + +void handle_dm_stop_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) +{ + char *erm; + + if ((err == 0) || (err == -EAGAIN)) { + printf("Got a stop message\n"); + state = STATE_IDLE; + } else { + erm = strerror(err*-1); + printf("Stop request failed, error: %s\n", erm); + } +} + +int enable_drop_monitor() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_START, NLM_F_REQUEST|NLM_F_ACK, 0); + + if (monitor_sw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_SW_DROPS)) + goto nla_put_failure; + + if (monitor_hw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_HW_DROPS)) + goto nla_put_failure; + + set_ack_cb(msg, handle_dm_start_msg); + + return send_netlink_message(msg); + +nla_put_failure: + free_netlink_msg(msg); + return -EMSGSIZE; +} + +int disable_drop_monitor() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_STOP, NLM_F_REQUEST|NLM_F_ACK, 0); + + if (monitor_sw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_SW_DROPS)) + goto nla_put_failure; + + if (monitor_hw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_HW_DROPS)) + goto nla_put_failure; + + set_ack_cb(msg, handle_dm_stop_msg); + + return send_netlink_message(msg); + +nla_put_failure: + free_netlink_msg(msg); + return -EMSGSIZE; +} + +int set_alert_mode() +{ + enum net_dm_alert_mode alert_mode; + struct netlink_message *msg; + + switch (state) { + case STATE_RQST_ALERT_MODE_SUMMARY: + alert_mode = NET_DM_ALERT_MODE_SUMMARY; + break; + case STATE_RQST_ALERT_MODE_PACKET: + alert_mode = NET_DM_ALERT_MODE_PACKET; + break; + default: + return -EINVAL; + } + + msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); + if (!msg) + return -ENOMEM; + + if (nla_put_u8(msg->nlbuf, NET_DM_ATTR_ALERT_MODE, alert_mode)) + goto nla_put_failure; + + set_ack_cb(msg, handle_dm_config_msg); + + return send_netlink_message(msg); + +nla_put_failure: + free_netlink_msg(msg); + return -EMSGSIZE; +} + +int set_trunc_len() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg->nlbuf, NET_DM_ATTR_TRUNC_LEN, trunc_len)) + goto nla_put_failure; + + set_ack_cb(msg, handle_dm_config_msg); + + return send_netlink_message(msg); + +nla_put_failure: + free_netlink_msg(msg); + return -EMSGSIZE; +} + +int set_queue_len() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg->nlbuf, NET_DM_ATTR_QUEUE_LEN, queue_len)) + goto nla_put_failure; + + set_ack_cb(msg, handle_dm_config_msg); + + return send_netlink_message(msg); + +nla_put_failure: + free_netlink_msg(msg); + return -EMSGSIZE; +} + +int get_config() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_CONFIG_GET, NLM_F_REQUEST, 0); + if (!msg) + return -ENOMEM; + + return send_netlink_message(msg); +} + +int get_stats() +{ + struct netlink_message *msg; + + msg = alloc_netlink_msg(NET_DM_CMD_STATS_GET, NLM_F_REQUEST, 0); + if (!msg) + return -ENOMEM; + + return send_netlink_message(msg); +} + +void display_help() +{ + printf("Command Syntax:\n"); + printf("exit\t\t\t\t - Quit dropwatch\n"); + printf("help\t\t\t\t - Display this message\n"); + printf("set:\n"); + printf("\talertlimit \t - capture only this many alert packets\n"); + printf("\talertmode \t - set mode to \"summary\" or \"packet\"\n"); + printf("\ttrunc \t\t - truncate packets to this length. "); + printf("Only applicable when \"alertmode\" is set to \"packet\"\n"); + printf("\tqueue \t\t - queue up to this many packets in the kernel. "); + printf("Only applicable when \"alertmode\" is set to \"packet\"\n"); + printf("\tsw \t - monitor software drops\n"); + printf("\thw \t - monitor hardware drops\n"); + printf("start\t\t\t\t - start capture\n"); + printf("stop\t\t\t\t - stop capture\n"); + printf("show\t\t\t\t - show existing configuration\n"); + printf("stats\t\t\t\t - show statistics\n"); +} + +void enter_command_line_mode() +{ + char *input; + int err; + + do { + input = readline("dropwatch> "); + + if (input == NULL) { + /* Someone closed stdin on us */ + printf("Terminating dropwatch...\n"); + state = STATE_EXIT; + break; + } + + if (!strcmp(input,"start")) { + state = STATE_RQST_ACTIVATE; + break; + } + + if (!strcmp(input, "stop")) { + state = STATE_RQST_DEACTIVATE; + break; + } + + if (!strcmp(input, "exit")) { + state = STATE_EXIT; + break; + } + + if (!strcmp (input, "help")) { + display_help(); + goto next_input; + } + + if (!strncmp(input, "set", 3)) { + char *ninput = input+4; + if (!strncmp(ninput, "alertlimit", 10)) { + alimit = strtoul(ninput+10, NULL, 10); + printf("setting alert capture limit to %lu\n", + alimit); + goto next_input; + } else if (!strncmp(ninput, "alertmode", 9)) { + ninput = ninput + 10; + if (!strncmp(ninput, "summary", 7)) { + state = STATE_RQST_ALERT_MODE_SUMMARY; + break; + } else if (!strncmp(ninput, "packet", 6)) { + state = STATE_RQST_ALERT_MODE_PACKET; + break; + } + } else if (!strncmp(ninput, "trunc", 5)) { + trunc_len = strtoul(ninput + 6, NULL, 10); + state = STATE_RQST_TRUNC_LEN; + break; + } else if (!strncmp(ninput, "queue", 5)) { + queue_len = strtoul(ninput + 6, NULL, 10); + state = STATE_RQST_QUEUE_LEN; + break; + } else if (!strncmp(ninput, "sw", 2)) { + err = strtobool(ninput + 3, &monitor_sw); + if (err) { + printf("invalid boolean value\n"); + state = STATE_FAILED; + break; + } + printf("setting software drops monitoring to %d\n", + monitor_sw); + goto next_input; + } else if (!strncmp(ninput, "hw", 2)) { + err = strtobool(ninput + 3, &monitor_hw); + if (err) { + printf("invalid boolean value\n"); + state = STATE_FAILED; + break; + } + printf("setting hardware drops monitoring to %d\n", + monitor_hw); + goto next_input; + } + } + + if (!strncmp(input, "show", 4)) { + state = STATE_RQST_CONFIG; + break; + } + + if (!strncmp(input, "stats", 5)) { + state = STATE_RQST_STATS; + break; + } +next_input: + free(input); + } while(1); + + free(input); +} + +void enter_state_loop(void) +{ + int should_rx = 0; + + while (1) { + switch(state) { + + case STATE_IDLE: + should_rx = 0; + enter_command_line_mode(); + break; + case STATE_RQST_ACTIVATE: + printf("Enabling monitoring...\n"); + if (enable_drop_monitor() < 0) { + perror("Unable to send activation msg:"); + state = STATE_FAILED; + } else { + state = STATE_ACTIVATING; + should_rx = 1; + } + break; + case STATE_ACTIVATING: + printf("Waiting for activation ack....\n"); + break; + case STATE_RECEIVING: + break; + case STATE_RQST_DEACTIVATE: + printf("Deactivation requested, turning off monitoring\n"); + if (disable_drop_monitor() < 0) { + perror("Unable to send deactivation msg:"); + state = STATE_FAILED; + } else + state = STATE_DEACTIVATING; + should_rx = 1; + break; + case STATE_DEACTIVATING: + printf("Waiting for deactivation ack...\n"); + break; + case STATE_EXIT: + case STATE_FAILED: + should_rx = 0; + return; + case STATE_RQST_ALERT_MODE_SUMMARY: + case STATE_RQST_ALERT_MODE_PACKET: + printf("Setting alert mode\n"); + if (set_alert_mode() < 0) { + perror("Failed to set alert mode"); + state = STATE_FAILED; + } else { + state = STATE_ALERT_MODE_SETTING; + should_rx = 1; + } + break; + case STATE_ALERT_MODE_SETTING: + printf("Waiting for alert mode setting ack...\n"); + break; + case STATE_RQST_TRUNC_LEN: + printf("Setting truncation length to %lu\n", + trunc_len); + if (set_trunc_len() < 0) { + perror("Failed to set truncation length"); + state = STATE_FAILED; + } else { + state = STATE_TRUNC_LEN_SETTING; + should_rx = 1; + } + break; + case STATE_TRUNC_LEN_SETTING: + printf("Waiting for truncation length setting ack...\n"); + break; + case STATE_RQST_QUEUE_LEN: + printf("Setting queue length to %lu\n", queue_len); + if (set_queue_len() < 0) { + perror("Failed to set queue length"); + state = STATE_FAILED; + } else { + state = STATE_QUEUE_LEN_SETTING; + should_rx = 1; + } + break; + case STATE_QUEUE_LEN_SETTING: + printf("Waiting for queue length setting ack...\n"); + break; + case STATE_RQST_CONFIG: + printf("Getting existing configuration\n"); + if (get_config() < 0) { + perror("Failed to get existing configuration"); + state = STATE_FAILED; + } else { + state = STATE_CONFIG_GETTING; + should_rx = 1; + } + break; + case STATE_CONFIG_GETTING: + printf("Waiting for existing configuration query response\n"); + break; + case STATE_RQST_STATS: + printf("Getting statistics\n"); + if (get_stats() < 0) { + perror("Failed to get statistics"); + state = STATE_FAILED; + } else { + state = STATE_STATS_GETTING; + should_rx = 1; + } + break; + case STATE_STATS_GETTING: + printf("Waiting for statistics query response\n"); + break; + default: + printf("Unknown state received! exiting!\n"); + state = STATE_FAILED; + should_rx = 0; + break; + } + + /* + * After we process our state loop, look to see if we have messages + */ + if (should_rx) + process_rx_message(); + } +} + +struct option options[] = { + {"lmethod", 1, 0, 'l'}, + {0, 0, 0, 0} +}; + +void usage() +{ + printf("dropwatch [-l|--lmethod ]\n"); +} + +int main (int argc, char **argv) +{ + int c, optind; + lookup_init_method_t meth = METHOD_NULL; + /* + * parse the options + */ + for(;;) { + c = getopt_long(argc, argv, "l:", options, &optind); + + /* are we done parsing ? */ + if (c == -1) + break; + + switch(c) { + + case '?': + usage(); + exit(1); + /* NOTREACHED */ + case 'l': + /* select the lookup method we want to use */ + if (!strncmp(optarg, "list", 4)) { + printf("Available lookup methods:\n"); + printf("kas - use /proc/kallsyms\n"); + exit(0); + } else if (!strncmp(optarg, "kas", 3)) { + meth = METHOD_KALLSYMS; + } else { + printf("Unknown lookup method %s\n", optarg); + exit(1); + } + break; + default: + printf("Unknown option\n"); + usage(); + exit(1); + /* NOTREACHED */ + } + } + + /* + * open up the netlink socket that we need to talk to our dropwatch socket + */ + nsd = setup_netlink_socket(); + + if (nsd == NULL) { + printf("Cleaning up on socket creation error\n"); + goto out; + } + + + /* + * Initialize our lookup library + */ + init_lookup(meth); + + enter_state_loop(); + printf("Shutting down ...\n"); + + nl_close(nsd); + exit(0); +out: + exit(1); +} diff --git a/src/net_dropmon.h b/src/net_dropmon.h new file mode 100644 index 0000000..f52af8e --- /dev/null +++ b/src/net_dropmon.h @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __NET_DROPMON_H +#define __NET_DROPMON_H + +#include + +struct net_dm_drop_point { + __u8 pc[8]; + __u32 count; +}; + +#define NET_DM_CFG_VERSION 0 +#define NET_DM_CFG_ALERT_COUNT 1 +#define NET_DM_CFG_ALERT_DELAY 2 +#define NET_DM_CFG_MAX 3 + +struct net_dm_config_entry { + __u32 type; + __u64 data __attribute__((aligned(8))); +}; + +struct net_dm_config_msg { + __u32 entries; + struct net_dm_config_entry options[0]; +}; + +struct net_dm_alert_msg { + __u32 entries; + struct net_dm_drop_point points[0]; +}; + +struct net_dm_user_msg { + union { + struct net_dm_config_msg user; + struct net_dm_alert_msg alert; + } u; +}; + + +/* These are the netlink message types for this protocol */ + +enum { + NET_DM_CMD_UNSPEC = 0, + NET_DM_CMD_ALERT, + NET_DM_CMD_CONFIG, + NET_DM_CMD_START, + NET_DM_CMD_STOP, + NET_DM_CMD_PACKET_ALERT, + NET_DM_CMD_CONFIG_GET, + NET_DM_CMD_CONFIG_NEW, + NET_DM_CMD_STATS_GET, + NET_DM_CMD_STATS_NEW, + _NET_DM_CMD_MAX, +}; + +#define NET_DM_CMD_MAX (_NET_DM_CMD_MAX - 1) + +/* + * Our group identifiers + */ +#define NET_DM_GRP_ALERT 1 + +enum net_dm_attr { + NET_DM_ATTR_UNSPEC, + + NET_DM_ATTR_ALERT_MODE, /* u8 */ + NET_DM_ATTR_PC, /* u64 */ + NET_DM_ATTR_SYMBOL, /* string */ + NET_DM_ATTR_IN_PORT, /* nested */ + NET_DM_ATTR_TIMESTAMP, /* u64 */ + NET_DM_ATTR_PROTO, /* u16 */ + NET_DM_ATTR_PAYLOAD, /* binary */ + NET_DM_ATTR_PAD, + NET_DM_ATTR_TRUNC_LEN, /* u32 */ + NET_DM_ATTR_ORIG_LEN, /* u32 */ + NET_DM_ATTR_QUEUE_LEN, /* u32 */ + NET_DM_ATTR_STATS, /* nested */ + NET_DM_ATTR_HW_STATS, /* nested */ + NET_DM_ATTR_ORIGIN, /* u16 */ + NET_DM_ATTR_HW_TRAP_GROUP_NAME, /* string */ + NET_DM_ATTR_HW_TRAP_NAME, /* string */ + NET_DM_ATTR_HW_ENTRIES, /* nested */ + NET_DM_ATTR_HW_ENTRY, /* nested */ + NET_DM_ATTR_HW_TRAP_COUNT, /* u32 */ + NET_DM_ATTR_SW_DROPS, /* flag */ + NET_DM_ATTR_HW_DROPS, /* flag */ + NET_DM_ATTR_FLOW_ACTION_COOKIE, /* binary */ + + __NET_DM_ATTR_MAX, + NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1 +}; + +/** + * enum net_dm_alert_mode - Alert mode. + * @NET_DM_ALERT_MODE_SUMMARY: A summary of recent drops is sent to user space. + * @NET_DM_ALERT_MODE_PACKET: Each dropped packet is sent to user space along + * with metadata. + */ +enum net_dm_alert_mode { + NET_DM_ALERT_MODE_SUMMARY, + NET_DM_ALERT_MODE_PACKET, +}; + +enum { + NET_DM_ATTR_PORT_NETDEV_IFINDEX, /* u32 */ + NET_DM_ATTR_PORT_NETDEV_NAME, /* string */ + + __NET_DM_ATTR_PORT_MAX, + NET_DM_ATTR_PORT_MAX = __NET_DM_ATTR_PORT_MAX - 1 +}; + +enum { + NET_DM_ATTR_STATS_DROPPED, /* u64 */ + + __NET_DM_ATTR_STATS_MAX, + NET_DM_ATTR_STATS_MAX = __NET_DM_ATTR_STATS_MAX - 1 +}; + +enum net_dm_origin { + NET_DM_ORIGIN_SW, + NET_DM_ORIGIN_HW, +}; + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..5d7df35 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,2 @@ +check_SCRIPTS = rundropwatch.sh +TESTS = rundropwatch.sh diff --git a/tests/rundropwatch.sh b/tests/rundropwatch.sh new file mode 100755 index 0000000..be8e132 --- /dev/null +++ b/tests/rundropwatch.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +abort_dropwatch() { + sleep 5 + killall -SIGINT dropwatch +} + +abort_dropwatch & +echo -e "set alertlimit 1\nstart\nstop\nexit" | ../src/dropwatch -l kas + +if [ $? -ne 0 ] +then + grep -q NET_DM ./rundropwatch.sh.log + if [ $? -eq 0 ] + then + # This platform doesn't support NET_DM, skip this test + exit 77 + fi +fi -- 2.30.2